#include <random>
#include "ranges"
#include "Board.h"

Board::Board(QWidget *widget) : widget(widget) {

}

Board::~Board() {
    for (Block *block: data) {
        delete block;
    }
}

void Board::reset(int r, int c, int n) {
    resize(r, c);
    initPosition();
    initMine(n);
    initNumber();
    is_game_over = false;
}

void Board::resize(int r, int c) {
    m_r = r;
    m_c = c;
    const int total = m_r * m_c;
    while (data.size() > total) {
        delete *data.erase(data.end() - 1);
    }
    for (auto block: data) {
        block->reset();
    }
    while (data.size() < total) {
        auto block = data.emplace_back(new Block(widget));
        connect(block, &Block::pressed, this, &Board::onPress);
        connect(block, &Block::marked, this, &Board::onMark);
        connect(block, &Block::entered, this, &Board::onEnter);
        connect(block, &Block::leaved, this, &Board::onLeave);
    }
}

Block *Board::operator[](int x, int y) {
    if (Block *block = get(x, y)) {
        return block;
    }
    throw std::runtime_error(std::format("Board索引越界：[{}, {}]", x, y));
}

Block *Board::get(int x, int y) {
    if (x < 0 || x >= m_c || y < 0 || y >= m_r) {
        return nullptr;
    }
    return data[y * m_c + x];
}

std::vector<Block *> Board::getNear(int x, int y) {
    std::vector<Block *> res;
    for (int dx = -1; dx <= 1; dx++) {
        for (int dy = -1; dy <= 1; dy++) {
            if (dx == 0 && dy == 0) {
                continue;
            }
            if (Block *block = get(x + dx, y + dy)) {
                res.push_back(block);
            }
        }
    }
    return res;
}

void Board::onPress(Block *block) {
    if (block->isPressed() || is_game_over) {
        return;
    }
    block->press();
    if (std::ranges::all_of(data, [](Block *block) {
        return block->getValue() == -1 || block->isPressed();
    })) {
        is_game_over = true;
        emit gameOver(true);
        return;
    }
    if (block->getValue() == 0) {
        auto [x, y] = block->getPosition();
        for (Block *near: getNear(x, y)) {
            if (near->state != Block::State::MARKED) {
                emit near->pressed(near);
            }
        }
    } else if (block->getValue() == -1) {
        block->setRed();
        for (Block *b: data) {
            if (b->getValue() == -1 && b->state != Block::State::MARKED) {
                b->press();
            } else if (b->getValue() != -1 && b->state == Block::State::MARKED) {
                b->setWrong();
            }
        }
        is_game_over = true;
        emit gameOver(false);
    }
}

void Board::onMark(Block *block) {
    if (block->isPressed() || is_game_over) {
        return;
    }
    block->mark();
    int remain = m_n - static_cast<int>(std::ranges::count_if(data, [](Block *b) {
        return b->state == Block::State::MARKED;
    }));
    emit updateRemain(remain);
    emit updateHover(block->state != Block::State::MARKED);
}

void Board::onEnter(Block *block) {
    if (is_game_over) {
        return;
    }
    emit updateHover(block->state != Block::State::MARKED);
}

void Board::onLeave(Block *) {
    if (is_game_over) {
        return;
    }
    emit updateHover(false);
}

void Board::initPosition() {
    auto it = data.begin();
    auto end = data.end();
    for (int y = 0; y < m_r; y++) {
        for (int x = 0; x < m_c; x++) {
            if (it != end) {
                (*it)->setPosition(x, y);
            } else {
                throw std::runtime_error("Block对象不足！");
            }
            ++it;
        }
    }
}

void Board::initMine(int n) {
    m_n = n;
    emit updateRemain(n);
    if (n > data.size()) {
        throw std::runtime_error("雷数过多");
    }
    std::default_random_engine e(std::random_device{}());
    std::uniform_int_distribution<int> u(0, static_cast<int>(data.size()) - 1); // 左闭右闭区间
    while (n) {
        int i = u(e);
        Block *block = data[i];
        if (block->getValue() != -1) {
            block->setValue(-1);
            --n;
        }
    }
}

void Board::initNumber() {
    for (Block *block: data) {
        if (block->getValue() == -1) {
            continue;
        }
        int num = 0;
        auto [x, y] = block->getPosition();
        for (Block *near: getNear(x, y)) {
            if (near->getValue() == -1) {
                ++num;
            }
        }
        block->setValue(num);
    }
}